package com.unbiz.common.collection;

/*
 * Copyright (C) 2007 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * Implementation of {@link Multimap} using hash tables.
 *
 * <p>The multimap does not store duplicate key-value pairs. Adding a new
 * key-value pair equal to an existing key-value pair has no effect.
 *
 * <p>Keys and values may be null. All optional multimap methods are supported,
 * and all returned views are modifiable.
 *
 * <p>This class is not threadsafe when any concurrent operations update the
 * multimap. Concurrent read operations will work correctly. To allow concurrent
 * update operations, wrap your multimap with a call to {@link
 * Multimaps#synchronizedSetMultimap}.
 *
 * @author Jared Levy
 * @since 2.0
 */
public final class HashMultimap<K, V> extends AbstractMapBasedMultimap<K, V> {
  private static final int DEFAULT_VALUES_PER_KEY = 2;

  transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY;

  /**
   * Creates a new, empty {@code HashMultimap} with the default initial
   * capacities.
   */
  public static <K, V> HashMultimap<K, V> create() {
    return new HashMultimap<K, V>();
  }

  /**
   * Constructs an empty {@code HashMultimap} with enough capacity to hold the
   * specified numbers of keys and values without rehashing.
   *
   * @param expectedKeys the expected number of distinct keys
   * @param expectedValuesPerKey the expected average number of values per key
   * @throws IllegalArgumentException if {@code expectedKeys} or {@code
   *      expectedValuesPerKey} is negative
   */
  public static <K, V> HashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) {
    return new HashMultimap<K, V>(expectedKeys, expectedValuesPerKey);
  }

  /**
   * Constructs a {@code HashMultimap} with the same mappings as the specified
   * multimap. If a key-value mapping appears multiple times in the input
   * multimap, it only appears once in the constructed multimap.
   *
   * @param multimap the multimap whose contents are copied to this multimap
   */
  public static <K, V> HashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
    return new HashMultimap<K, V>(multimap);
  }

  private HashMultimap() {
    super(new HashMap<K, Collection<V>>());
  }

  private HashMultimap(int expectedKeys, int expectedValuesPerKey) {
    super(new HashMap<K, Collection<V>>(1+expectedKeys*4/3));
  
    this.expectedValuesPerKey = expectedValuesPerKey;
  }

  private HashMultimap(Multimap<? extends K, ? extends V> multimap) {
    this(multimap.keySet().size(),DEFAULT_VALUES_PER_KEY);
    putAll(multimap);
  }

  /**
   * {@inheritDoc}
   *
   * <p>Creates an empty {@code HashSet} for a collection of values for one key.
   *
   * @return a new {@code HashSet} containing a collection of values for one key
   */
  @Override
  Set<V> createCollection(K key) {
    return new HashSet<V>(expectedValuesPerKey);
  }

  /**
   * @serialData expectedValuesPerKey, number of distinct keys, and then for
   *     each distinct key: the key, number of values for that key, and the
   *     key's values
   */
 
  private void writeObject(ObjectOutputStream stream) throws IOException {
    stream.defaultWriteObject();
    writeMultimap(this, stream);
  }

 
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    expectedValuesPerKey = DEFAULT_VALUES_PER_KEY;
    int distinctKeys = stream.readInt();
    Map<K, Collection<V>> map = new HashMap();
    setMap(map);
    populateMultimap(this, stream, distinctKeys);
  }

 
  private static final long serialVersionUID = 0;
  
  /**
   * Stores the contents of a multimap in an output stream, as part of
   * serialization. It does not support concurrent multimaps whose content may
   * change while the method is running. The {@link Multimap#asMap} view
   * determines the ordering in which data is written to the stream.
   *
   * <p>The serialized output consists of the number of distinct keys, and then
   * for each distinct key: the key, the number of values for that key, and the
   * key's values.
   */
  static <K, V> void writeMultimap(Multimap<K, V> multimap, ObjectOutputStream stream)
      throws IOException {
    stream.writeInt(multimap.asMap().size());
    for (Map.Entry<K, Collection<V>> entry : multimap.asMap().entrySet()) {
      stream.writeObject(entry.getKey());
      stream.writeInt(entry.getValue().size());
      for (V value : entry.getValue()) {
        stream.writeObject(value);
      }
    }
  }

  /**
   * Populates a multimap by reading an input stream, as part of
   * deserialization. See {@link #writeMultimap} for the data format.
   */
  static <K, V> void populateMultimap(Multimap<K, V> multimap, ObjectInputStream stream)
      throws IOException, ClassNotFoundException {
    int distinctKeys = stream.readInt();
    populateMultimap(multimap, stream, distinctKeys);
  }
  
  /**
   * Populates a multimap by reading an input stream, as part of
   * deserialization. See {@link #writeMultimap} for the data format. The number
   * of distinct keys is determined by a prior call to {@link #readCount}.
   */
  static <K, V> void populateMultimap(
      Multimap<K, V> multimap, ObjectInputStream stream, int distinctKeys)
      throws IOException, ClassNotFoundException {
    for (int i = 0; i < distinctKeys; i++) {
      @SuppressWarnings("unchecked") // reading data stored by writeMultimap
      K key = (K) stream.readObject();
      Collection<V> values = multimap.get(key);
      int valueCount = stream.readInt();
      for (int j = 0; j < valueCount; j++) {
        @SuppressWarnings("unchecked") // reading data stored by writeMultimap
        V value = (V) stream.readObject();
        values.add(value);
      }
    }
  }
}
